import { Direction, Status, convertToGrid } from '../constants'
import Snake from './Snake'

const candy = 'candy'
export default class Board {
  private readonly boards: string[]
  private readonly snake: Snake
  constructor(
    public readonly width: number,
    public readonly height: number,
    public readonly withWalls: boolean
  ) {
    this.boards = new Array(width * height).fill('')
    this.boards[3] = candy
    this.snake = new Snake([2, 1, 0])
    this.boards[0] = 'snake to-right tail'
    this.boards[1] = 'snake from-left to-right'
    this.boards[2] = 'snake from-left head'
  }

  get score() {
    return this.snake.length * 100
  }

  willCollide(oldHead: number, newHead: number, direction: Direction) {
    if (this.snake.occupiesBoardIndex(newHead)) return true
    if (!this.withWalls) return false
    const { row: oldRow, column: oldColumn } = convertToGrid(
      oldHead,
      this.width
    )
    const { row: newRow, column: newColumn } = convertToGrid(
      newHead,
      this.width
    )

    if (direction === Direction.RIGHT && newColumn < oldColumn) return true
    if (direction === Direction.LEFT && newColumn > oldColumn) return true
    if (direction === Direction.UP && newRow > oldRow) return true
    if (direction === Direction.DOWN && newRow < oldRow) return true
    return false
  }

  nextTick(direction: Direction): Status {
    const oldTailIndex = this.snake.tail
    const oldHeadIndex = this.snake.head
    const newHeadIndex = this.snake.nextHead(direction, this.width, this.height)
    if (this.willCollide(oldHeadIndex, newHeadIndex, direction))
      return Status.COLLIDED

    const ateCandy = this.boards[newHeadIndex] === candy
    this.snake.move(ateCandy, newHeadIndex)

    this.boards[newHeadIndex] = `snake from-${this.snake
      .invertedMovementDirection(this.width, this.height)
      .toLowerCase()} head`
    const oldHeadClasses = this.boards[oldHeadIndex].split(' ')
    this.boards[oldHeadIndex] =
      oldHeadClasses.slice(0, oldHeadClasses.length - 1).join(' ') +
      ` to-${direction.toLowerCase()}`
    if (!ateCandy && oldTailIndex !== newHeadIndex)
      this.boards[oldTailIndex] = ''

    if (ateCandy) {
      this.createRandomCandy()
    } else {
      const newTailIndex = this.snake.tail
      const oldTailClasses = this.boards[newTailIndex].split(' ')
      this.boards[newTailIndex] = `snake tail ${
        oldTailClasses[oldTailClasses.length - 1]
      }`
    }
    if (ateCandy) return Status.ATE
    return Status.MOVED
  }

  serialize(): string[] {
    return [...this.boards]
  }

  allowedMovementDirections() {
    const allDirections = Object.values(Direction)
    if (this.snake.length < 2) return allDirections
    return [Direction.UP, Direction.DOWN, Direction.LEFT, Direction.RIGHT]
  }

  createRandomCandy() {
    const freeIndices = this.boards
      .map((item, index) => {
        if (item) return null
        return index
      })
      .filter(item => item) as number[]
    const number = Math.floor(Math.random() * freeIndices.length)
    const candyIndex = freeIndices[number]
    this.boards[candyIndex] = candy
  }
}
